<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Bubbles</title>
    <style>
      .g6-tooltip {
        border: 1px solid #e2e2e2;
        border-radius: 4px;
        font-size: 12px;
        color: #545454;
        background-color: rgba(255, 255, 255, 0.9);
        padding: 10px 8px;
        box-shadow: rgb(174, 174, 174) 0px 0px 10px;
      }
    </style>
  </head>

  <body>
    <div id="tip"></div>
    <div id="mountNode"></div>
    <script src="../build/g6.js"></script>
    <script src="./assets/jquery-3.2.1.min.js"></script>
    <script>
      const colors = [
        'rgb(64, 174, 247)',
        'rgb(108, 207, 169)',
        'rgb(157, 223, 125)',
        'rgb(240, 198, 74)',
      ];

      const data = {
        nodes: [
          {
            id: '0',
            label: '0',
            value: 10,
            cluster: 'a',
            description: 'this is node 0, \nand the value of it is 10',
          },
          {
            id: '1',
            label: '1',
            value: 20,
            cluster: 'b',
            description: 'this is node 1, \nand the value of it is 20',
          },
          {
            id: '2',
            label: '2',
            value: 5,
            cluster: 'a',
            description: 'this is node 2, \nand the value of it is 5',
          },
          {
            id: '3',
            label: '3',
            value: 10,
            cluster: 'a',
            description: 'this is node 3, \nand the value of it is 10',
          },
          {
            id: '4',
            label: '4',
            value: 12,
            cluster: 'c',
            subCluster: 'sb',
            description: 'this is node 4, \nand the value of it is 12',
          },
          {
            id: '5',
            label: '5',
            value: 18,
            cluster: 'c',
            subCluster: 'sa',
            description: 'this is node 5, \nand the value of it is 18',
          },
          {
            id: '6',
            label: '6',
            value: 3,
            cluster: 'c',
            subCluster: 'sa',
            description: 'this is node 6, \nand the value of it is 3',
          },
          {
            id: '7',
            label: '7',
            value: 7,
            cluster: 'b',
            subCluster: 'sa',
            description: 'this is node 7, \nand the value of it is 7',
          },
          {
            id: '8',
            label: '8',
            value: 21,
            cluster: 'd',
            subCluster: 'sb',
            description: 'this is node 8, \nand the value of it is 21',
          },
          {
            id: '9',
            label: '9',
            value: 9,
            cluster: 'd',
            subCluster: 'sb',
            description: 'this is node 9, \nand the value of it is 9',
          },
        ],
        edges: [],
      };

      const graph = new G6.Graph({
        container: 'mountNode',
        width: 1000,
        height: 800,
        layout: {
          type: 'force',
          nodeStrength: 30,
          collideStrength: 0.7,
          alphaDecay: 0.01,
          preventOverlap: true,
        },
        modes: {
          default: ['drag-node'],
        },
        defaultNode: {
          size: [10, 10],
          color: 'steelblue',
          style: {
            opacity: 0.8,
            lineWidth: 1,
            stroke: '#999',
          },
        },
        defaultEdge: {
          size: 1,
          color: '#e2e2e2',
        },
        nodeStateStyles: {
          click: {
            opacity: 1,
          },
        },
      });

      function refreshDragedNodePosition(e) {
        const model = e.item.get('model');
        model.fx = e.x;
        model.fy = e.y;
      }
      graph.on('node:dragstart', e => {
        graph.layout();
        refreshDragedNodePosition(e);
      });
      graph.on('node:drag', e => {
        refreshDragedNodePosition(e);
      });
      graph.on('node:dragend', e => {
        e.item.get('model').fx = null;
        e.item.get('model').fy = null;
      });
      graph.on('node:click', e => {
        const node = e.item;
        const states = node.getStates();
        let clicked = false;
        const model = node.getModel();
        let size = 200;
        let labelText = 'NODE:' + model.id + '\n' + model.description;
        states.forEach(state => {
          if (state === 'click') {
            clicked = true;
            size = model.oriSize;
            labelText = model.oriLabel;
          }
        });
        graph.setItemState(node, 'click', !clicked);
        graph.updateItem(node, {
          size,
          label: labelText,
        });
        graph.layout();
      });

      // mapping
      const edges = data.edges;
      const nodes = data.nodes;
      const nodeMap = new Map();
      const clusterMap = new Map();
      let clusterId = 0;
      nodes.forEach((node, i) => {
        nodeMap.set(node.id, node);
        // cluster
        if (node.cluster && clusterMap.get(node.cluster) === undefined) {
          clusterMap.set(node.cluster, clusterId);
          clusterId++;
        }
        const cid = clusterMap.get(node.cluster);
        if (node.style) {
          node.style.fill = colors[cid % colors.length];
        } else {
          node.style = {
            fill: colors[cid % colors.length],
          };
        }
      });

      // map the value to node size
      let maxNodeValue = -9999,
        minNodeValue = 9999;
      nodes.forEach(n => {
        if (maxNodeValue < n.value) maxNodeValue = n.value;
        if (minNodeValue > n.value) minNodeValue = n.value;
      });
      const nodeSizeRange = [10, 30];
      const nodeSizeDataRange = [minNodeValue, maxNodeValue];
      scaleNodeProp(nodes, 'size', 'value', nodeSizeDataRange, nodeSizeRange);

      nodes.forEach(node => {
        node.oriSize = node.size;
        node.oriLabel = node.label;
      });

      graph.data(data);
      graph.render();
      function scaleNodeProp(elements, propName, refPropName, dataRange, outRange) {
        const outLength = outRange[1] - outRange[0];
        const dataLength = dataRange[1] - dataRange[0];
        elements.forEach(n => {
          if (propName.split('.')[0] === 'style') {
            if (n.style) {
              n.style[propName.split('.')[1]] =
                ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0];
            } else {
              n.style = {
                [propName.split('.')[1]]:
                  ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0],
              };
            }
          } else {
            n[propName] = ((n[refPropName] - dataRange[0]) * outLength) / dataLength + outRange[0];
          }
        });
      }
    </script>
  </body>
</html>
